#include<stdio.h>
#include<stdlib.h>
#include <sys/types.h>          /* See NOTES */
#include <sys/socket.h>
#include<arpa/inet.h>
#include<unistd.h>
#include<string.h>
#include <stdbool.h>

#define N 128

#define ERRLOG(errmsg) do{\
                                perror(errmsg);\
                                printf("file : %s, func: %s, line : %d", __FILE__, __func__, __LINE__);\
                                exit(-1);\
                         }while(0);

typedef struct{
    int code;
    char name[32];
    char text[32];
}MSG;

typedef struct node{
    char name[32];
    struct sockaddr_in addr;
    struct node *next;
}linklist;

linklist* linklistCreate();
void udpChatLogin(int sockfd, MSG msg, linklist *h, struct sockaddr_in clientaddr);
void udpBroadcast(int sockfd, MSG msg, linklist *h, struct sockaddr_in clientaddr);
void udpChatQuit(int sockfd, MSG msg, linklist *h, struct sockaddr_in clientaddr);
void udpPrivateChat(int sockfd, MSG msg, linklist *h);

int main(int argc, char const *argv[]){
    if(argc != 3){
        fprintf(stderr, "Usage %s <ip> <port>\n", argv[0]);
        exit(-1);
    }
    int sockfd = -1;
    struct sockaddr_in servaddr, clientaddr;
    socklen_t addrlen = sizeof(servaddr);
    if((sockfd = socket(AF_INET, SOCK_DGRAM, 0)) == -1){
        ERRLOG("socket error");
    }

    //填充网络信息结构体
    memset(&servaddr, 0, sizeof(servaddr));
    servaddr.sin_family = AF_INET;
    servaddr.sin_addr.s_addr = inet_addr(argv[1]);
    servaddr.sin_port = htons(atoi(argv[2]));

    if(bind(sockfd, (struct sockaddr *)&servaddr, addrlen) == -1){
        ERRLOG("bind error");
    }

    pid_t pid;
    if((pid = fork()) == -1){
        ERRLOG("fork error");
    }else if(pid > 0){
        MSG msg;
        //设置操作为2 —— 代表"群聊操作"
        msg.code = 2;
        //设置用户名
        strcpy(msg.name, "server");
        while(1){
            //要发送的系统消息
            fgets(msg.text, 32, stdin);
            msg.text[strlen(msg.text) - 1] = '\0';
            if(sendto(sockfd, &msg, sizeof(msg), 0, (struct sockaddr *)&servaddr, addrlen) == -1){
                ERRLOG("sendto error");
            }
        }
    }else{
        //子进程接收数据并处理
        MSG msg;
        linklist *h = linklistCreate();
        while(1){
            if(recvfrom(sockfd, &msg, sizeof(msg), 0, (struct sockaddr *)&clientaddr, &addrlen) == -1){
                ERRLOG("recvfrom error");
            }
            //根据接收数据的操作码做出相应的处理
            switch(msg.code){
                case 1: //登陆操作
                    udpChatLogin(sockfd, msg, h, clientaddr);
                    break;
                case 2: //群聊操作
                    udpBroadcast(sockfd, msg, h, clientaddr);
                    break;
                case 3: //退出操作
                    udpChatQuit(sockfd, msg, h, clientaddr);
                    break;
                case 4: //私聊操作
                    udpPrivateChat(sockfd, msg, h);
                    break;
            }
        }
    }
    return 0;
}

linklist* linklistCreate(){
    linklist *h = (linklist *)malloc(sizeof(linklist));
    h->next = NULL;
    return h;
}

void udpChatLogin(int sockfd, MSG msg, linklist *h, struct sockaddr_in clientaddr){
    linklist *p = h;
    //遍历链表判断用户名是否存在
    while(p->next != NULL){
        p = p->next;
        if(strcmp(p->name, msg.name) == 0){
            //-1标识登陆出错
            msg.code = -1;
            strcpy(msg.text, "User name exists");
            if(sendto(sockfd, &msg, N, 0, (struct sockaddr *)&clientaddr, sizeof(clientaddr)) == -1){
                ERRLOG("sento error");
            }
            return ;
        }
    }
    strcpy(msg.text, "Successfully logged in");
    if(sendto(sockfd, &msg, N, 0, (struct sockaddr *)&clientaddr, sizeof(clientaddr)) == -1){
        ERRLOG("sento error");
    }
    printf("%s 上线\n", msg.name);

    //将上线数据发送给所有的在线用户
    //遍历链表发送数据
    p = h;
    char buf[N] = {0};
    sprintf(buf, "------------------------------%s 上线了------------------------------", msg.name);
    stpcpy(msg.text, buf);
    while(p->next != NULL){
        p = p->next;
        if(sendto(sockfd, &msg, N, 0, (struct sockaddr *)&p->addr, sizeof(p->addr)) == -1){
            ERRLOG("sendto error");
        }
    }
    //将新用户的信息保存在链表中
    linklist *temp = (linklist *)malloc(sizeof(linklist));
    strcpy(temp->name, msg.name);
    temp->addr = clientaddr;
    temp->next = NULL;

    temp->next = h->next;
    h->next = temp;
    return ;
}

void udpBroadcast(int sockfd, MSG msg, linklist *h, struct sockaddr_in clientaddr){
    //将用户说的话组包发送给所有的在线用户
    char buf[N] = {0};
    printf("%s : %s\n", msg.name, msg.text);
    sprintf(buf, "%s : %s", msg.name, msg.text);
    strcpy(msg.text, buf);

    //遍历链表发送信息包
    linklist *p = h;
    while(p->next != NULL){
        p = p->next;
        //发送者自己不接收数据
        //memcmp：比较两块内存区域的内容是否一致
        if(memcmp(&p->addr, &clientaddr, sizeof(clientaddr)) != 0){
            if(sendto(sockfd, &msg, N, 0, (struct sockaddr *)&p->addr, sizeof(p->addr)) == -1){
                ERRLOG("sendto error");
            }
        }
    }
}

void udpChatQuit(int sockfd, MSG msg, linklist *h, struct sockaddr_in clientaddr){
    //组包发送下线信息
    char buf[N] = {0};
    printf("%s 下线\n", msg.name);
    sprintf(buf, "------------------------------%s 下线了------------------------------", msg.name);
    strcpy(msg.text, buf);
    //遍历链表并发送
    linklist *p = h;
    linklist *temp;
    while(p->next != NULL){
        //将要退出的用户信息从链表中移除
        if(memcmp(&p->next->addr, &clientaddr, sizeof(clientaddr)) == 0){
            temp = p->next;
            p->next = temp->next;

            free(temp);
            temp = NULL;
        }else{
            //发送下线消息
            if(sendto(sockfd, &msg, N, 0, (struct sockaddr *)&p->next->addr, sizeof(p->next->addr)) == -1){
                ERRLOG("sendto error");
            }
            p = p->next;
        }
    }
}

void udpPrivateChat(int sockfd, MSG msg, linklist *h){
    char name[N] = {0};
    char text[N] = {0};
    //flag为false代表用户名不存在，否则为存在
    bool flag = false;
    int i = strlen("private to ");
    int j = 0;
    for(; msg.text[i] != ' '; i++, j++){
        name[j] = msg.text[i];
    }
    j = 0;
    while(msg.text[i] != '\0'){
        text[j] = msg.text[i];
        i++;
        j++;
    }
    strcpy(msg.text, text);

    //遍历链表并发送
    linklist *p = h;
    while(p->next != NULL){
        p = p->next;

        if(strcmp(p->name, name) == 0){
            if(sendto(sockfd, &msg, 128, 0, (struct sockaddr *)&p->addr, sizeof(p->addr)) == -1){
                ERRLOG("sendto error");
            }
            flag = true;
        }
    }
    if(!flag){
        //此时用户名不存在
        //发送私聊用户不存在的信息给A用户
        if(sendto(sockfd, &msg, 128, 0, (struct sockaddr *)&p->addr, sizeof(p->addr)) == -1){
            ERRLOG("sendto error");
        } 
    }
}